<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    
    <title>Kotlin实战 | 语法糖 | ApocalypseBlog</title>
    <canvas id="header_canvas"style="position:absolute;bottom:0"></canvas> 
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    

    

    

    

    
<link rel="stylesheet" href="/dist/build.css?v=1.14.0.css">

    <script src="/javascripts/bubble.js"></script>
    <script>
        window.isPost = true
        window.aomori = {
            
            
        }
        window.aomori_logo_typed_animated = false
        window.aomori_search_algolia = false

    </script>
<script>
    ((window.gitter = {}).chat = {}).options = {
      room: 'ApocalypseBlog/Apocalypse'
    };
  </script>
  <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<meta name="generator" content="Hexo 5.3.0"></head>
<!--DaoVoice服务http://blog.daovoice.io/-->

    <script>(function (i, s, o, g, r, a, m) {
        i['DaoVoiceObject'] = r;
        i[r] = i[r] ||
          function () {
            (i[r].q = i[r].q || []).push(arguments);
          };
        i[r].l = 1 * new Date();
        a = s.createElement(o);
        m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        a.charset = 'utf-8';
        m.parentNode.insertBefore(a, m);
      })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/b00f5052.js", 'daovoice');
      daovoice('init', {
        app_id: "b00f5052",
      });
      daovoice('update');
    </script>
  
<body>

    <div class="container">
    <header class="header">
        <div class="header-type">
            
            <div class="header-type-inner">
                
                    <a class="header-type-title" href="/">ApocalypseBlog</a>
                
    
                
            </div>
        </div>
        <div class="header-menu">
            <div class="header-menu-inner">
                
            </div>
            <div class="header-menu-social">
                
            </div>
        </div>

        <div class="header-menu-mobile">
            <div class="header-menu-mobile-inner" id="mobile-menu-open">
                <i class="icon icon-menu"></i>
            </div>
        </div>
    </header>

    <div class="header-menu-mobile-menu">
        <div class="header-menu-mobile-menu-bg"></div>
        <div class="header-menu-mobile-menu-wrap">
            <div class="header-menu-mobile-menu-inner">
                <div class="header-menu-mobile-menu-close" id="mobile-menu-close">
                    <i class="icon icon-cross"></i>
                </div>
                <div class="header-menu-mobile-menu-list">
                    
                </div>
            </div>
        </div>
    </div>

</div>

    <div class="container">
        <div class="main">
            <section class="inner">
                <section class="inner-main">
                    <div class="post">
    <article id="post-ckk6o6auo00097kny2sq69cxe" class="article article-type-post" itemscope
    itemprop="blogPost">

    <div class="article-inner">

        
          
        
        
        

        
        <header class="article-header">
            
  
    <h1 class="article-title" itemprop="name">
      Kotlin实战 | 语法糖
    </h1>
  

        </header>
        

        <div class="article-more-info article-more-info-post hairline">

            <div class="article-date">
  <time datetime="2020-06-13T18:15:46.000Z" itemprop="datePublished">2020-06-14</time>
</div>

            

            

            

        </div>

        <div class="article-entry post-inner-html hairline" itemprop="articleBody">
            <p>学习了 Kotlin 后，写代码时常有一种“闹革命”的冲动，总是希望运用语法糖推翻“旧世界”（这样不好，项目会 delay 的~）。本文归纳了 Kotlin 语法糖在项目实战中的综合运用，以实际问题为索引，在分析解决方案的同时介绍相关语法知识。</p>
<a id="more"></a>

<h2 id="将-px-值转换成-dp-值"><a href="#将-px-值转换成-dp-值" class="headerlink" title="将 px 值转换成 dp 值"></a>将 px 值转换成 dp 值</h2><p>在非 xml 环境下构建布局，需要将 px 转换为 dp 来进行多屏幕适配。Java 的做法是在<code>Util</code>类中新增一个静态函数。利用 Kotlin 的扩展属性可以更简洁地实现：</p>
<pre><code>val Int.dp: Int
    get() &#123;
        return TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            this.toFloat(),
            Resources.getSystem().displayMetrics
        ).toInt()
    &#125;
</code></pre>
<p>为 Int 扩展一个属性<code>dp</code>，它的类型是 Int。在<code>get()</code>中定义该属性的取值算法。</p>
<p>然后就可以像这样动态地将 Int 值 dp 化：</p>
<pre><code>viewGroup.addView( textView, LayoutParam( 40.dp, 50.dp ) )
</code></pre>
<h2 id="弃用Builder模式"><a href="#弃用Builder模式" class="headerlink" title="弃用Builder模式"></a>弃用Builder模式</h2><p>当构造复杂对象时，需要很多参数，如果将所有参数都通过一个构造函数来传递，缺乏灵活性，但如果重载若干个带有不同参数的构造函数，代码就变得臃肿。Builder 模式可以简化构建过程。</p>
<p>在 Java 中 Builder模式 代码如下：</p>
<pre><code>public class Person &#123;
    //&#39;必选参数&#39;
    private String name;
    //&#39;以下都是可选参数&#39;
    private int gender;
    private int age;
    private int height;
    private int weight;

    //&#39;私有构造函数，限制必须通过构造者构建对象&#39;
    private Person(Builder builder) &#123;
        this.name = builder.name;
        this.gender = builder.gender;
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
    &#125;

    //&#39;构造者&#39;
    public static class Builder &#123;
        private String name;
        private int gender;
        private int age;
        private int height;
        private int weight;

        //&#39;必选参数必须在构造函数中传入&#39;
        public Builder(String name) &#123;
            this.name = name;
        &#125;

        //&#39;以下是每个非必要属性的设值函数，它返回构造者本身用于链式调用&#39;
        public Builder age(int age) &#123;
            this.age = age;
            return this;
        &#125;

        public Builder gender(int gender) &#123;
            this.gender = gender;
            return this;
        &#125;

        public Builder height(int height) &#123;
            this.height = height;
            return this;
        &#125;

        public Builder weight(int weight) &#123;
            this.weight = weight;
            return this;
        &#125;

        //&#39;构建对象&#39;
        public Person build() &#123;
            return new Person(this);
        &#125;
    &#125;

</code></pre>
<p>然后就可以像这样构建<code>Person</code>实例：</p>
<pre><code>//&#39;使用 Builder模式&#39;
Person p = new Person.Builder(&quot;taylor&quot;)
            .age(50)
            .gender(1)
            .weight(43)
            .build();

//&#39;使用构造函数&#39;
Person p2 = new Person(&quot;taylor&quot;, 50, 1, 0, 43);
</code></pre>
<p>对比之下，Builder模式 有两个优势：</p>
<ol>
<li>为参数标注语义：在Builder模式中，每个属性的赋值都是一个函数，函数名标注了属性语义。而直接使用构造函数时，很难分辨<code>50</code>，<code>43</code>哪个是年龄，哪个是体重。</li>
<li>可选参数：Builder模式中，除了必选参数，其他参数是可选的。但直接使用构造函数必须为所有参数赋值，比如上例中第四个参数身高被赋值为0。</li>
</ol>
<p>但 Builder模式 也有代价，新增了一个中间类<code>Builder</code>。</p>
<p>使用 Kotlin 的<code>命名参数</code>+<code>参数默认值</code>+<code>数据类</code>语法，在没有任何副作用的情况下就能实现 Builder模式：</p>
<pre><code>//&#39;将Person定义为数据类&#39;
data class Person(
    var name: String,
    //&#39;为以下可选参数设置默认值&#39;
    var gender: Int = 1,
    var age: Int= 0,
    var height: Int = 0,
    var weight: Int = 0
)

//&#39;使用命名参数构建Person实例&#39;
val p  = Person(name = “taylor”,gender = 1,weight = 43)
</code></pre>
<p>关于<code>数据类</code>、<code>参数默认值</code>、<code>命名参数</code>更详细的介绍可以点击<a target="_blank" rel="noopener" href="https://juejin.im/post/5cdb9536e51d456e5c5babd4">这里</a></p>
<p>如果想增加参数约束条件可以调用<code>require()</code>方法：</p>
<pre><code>data class Person(
    var name: String,
    var gender: Int = 1,
    var age: Int= 0,
    var height: Int = 0,
    var weight: Int = 0
)&#123;
    //&#39;在构造函数被调用的时候执行参数合法检查&#39;
    init &#123;
        require(name.isNotEmpty())&#123;”name cant be empty“&#125;
    &#125;
&#125;
</code></pre>
<p>此时如果像下面这样构造 Person，则会抛出异常：</p>
<pre><code>val p = Person(name=&quot;&quot;,gender = 1)
java.lang.IllegalArgumentException: name cant be empty
</code></pre>
<h2 id="打印列表、map"><a href="#打印列表、map" class="headerlink" title="打印列表、map"></a>打印列表、map</h2><p>调试程序时，经常需要打印列表内容，通常会这样打印：</p>
<pre><code>for (String str:list) &#123;
    Log.v(&quot;test&quot;, &quot;str=&quot;+str);
&#125;
</code></pre>
<p>不同业务界面的数据类型不同，为了调试，这样的 for 循环就会散落在各处，而且列表内容会分若干条 log 输出，中间极有可能被别的log打断。</p>
<p><strong>有没有一个函数可以打印包含任意数据类型的列表，并将列表内容组织成更具可读性的字符串？</strong></p>
<p>用 Kotlin  的<code>扩展函数</code>+<code>泛型</code>+<code>高阶函数</code>就能优雅地做到：</p>
<pre><code>fun &lt;T&gt; Collection&lt;T&gt;.print(map: (T) -&gt; String) =
    StringBuilder(&quot;\n[&quot;).also &#123; sb -&gt;
        //&#39;遍历集合元素，通过 map 表达式将元素转换成感兴趣的字串，并独占一行&#39;
        this.forEach &#123; e -&gt; sb.append(&quot;\n\t$&#123;map(e)&#125;,&quot;) &#125;
        sb.append(&quot;\n]&quot;)
    &#125;.toString()
</code></pre>
<p>为集合的基类<code>Collection</code>新增一个扩展函数，它是一个高阶函数，因为它的参数是另一个函数，该函数用 lambda 表示。再把集合元素抽象成泛型。通过<code>StringBuilder</code>将所有集合内容拼接成一个自动换行的字符串。</p>
<p>写段测试代码看下效果：</p>
<pre><code>data class Person(var name: String, var age: Int)

val persons = listOf(
    Person(&quot;Peter&quot;, 16),
    Person(&quot;Anna&quot;, 28),
    Person(&quot;Anna&quot;, 23),
    Person(&quot;Sonya&quot;, 39)
)

persons.print &#123; &quot;$&#123;it.name&#125;_$&#123;it.age&#125;&quot; &#125;.let &#123; Log.v(&quot;test&quot;,it) &#125;
</code></pre>
<p>打印结果如下：</p>
<pre><code>V/test: [
        Peter_16,
        Anna_28,
        Anna_23,
        Sonya_39,
    ]
</code></pre>
<p>同样地，可以如法炮制一个打印 map 的扩展函数：</p>
<pre><code>fun &lt;K, V&gt; Map&lt;K, V?&gt;.print(map: (V?) -&gt; String): String =
    StringBuilder(&quot;\n&#123;&quot;).also &#123; sb -&gt;
        this.iterator().forEach &#123; entry -&gt;
            sb.append(&quot;\n\t[$&#123;entry.key&#125;] = $&#123;map(entry.value)&#125;&quot;)
        &#125;
        sb.append(&quot;\n&#125;&quot;)
    &#125;.toString()
</code></pre>
<h2 id="将-data-类转换成-map"><a href="#将-data-类转换成-map" class="headerlink" title="将 data 类转换成 map"></a>将 data 类转换成 map</h2><p>有些数据类字段比较多，调试时，想把它们通通打印出来，在 Java 中，借助于 AndroidStudio 的 <code>toString</code>功能倒是可以方便地生成可读性很高的字串：</p>
<pre><code>public class Person &#123;
    private String name;
    private int age;

    @Override
    public String toString() &#123;
        return ”Person&#123;“ +
                ”name=‘“ + name + ’\” +
                ”, age=“ + age +
                ‘&#125;’;
    &#125;
&#125;
</code></pre>
<p>但是每新建一个数据类都要手动生成一个<code>toString()</code>方法也挺麻烦。</p>
<p>利用 Kotlin 的 <code>data class</code>可以省去这一步，但打印效果是所有字段都在同一行中：</p>
<pre><code>data class Person(var name: String, var age: Int)

Log.v(“test”, “person=$&#123;Person(&quot;Peter&quot;, 16)&#125;”)

//输出如下：
V/test: person=Person(name=Peter, age=16)
</code></pre>
<p>如果字段很多，把它们都打印在一行中可读性很差。</p>
<p><strong>有没有一种方法，可以读取一个类中所有的字段信息？</strong> 这样我们就可以将他们组织成想要的形状。请看下面这个方法：</p>
<pre><code>fun Any.ofMap() =
    //&#39;过滤掉除data class以外的其他类&#39;
    this::class.takeIf &#123; it.isData &#125;
        //&#39;遍历类的所有成员，过滤掉成员方法，只考虑成员属性&#39;
        ?.members?.filterIsInstance&lt;KProperty&lt;Any&gt;&gt;()
        //&#39;将成员属性名和值存储在Pair中&#39;
        ?.map &#123; it.name to it.call(this) &#125;
        //&#39;将Pair转换成map&#39;
        ?.toMap()
</code></pre>
<p>为任意 Kotlin 中的类添加一个<code>扩展函数</code>，它的功能是将<code>data class</code>中所有的字段名及其对应值存在一个 map 中。其中用到的 Kotlin 语法糖如下：</p>
<ul>
<li><code>isData</code>是<code>KClass</code>中的一个属性，用于判断该类是不是一个<code>data class</code>。<code>KClass</code>是 Kotlin 中用来描述 <strong>类的类型</strong>，<code>KClass</code>可以通过<code>对象::class</code>语法获得。</li>
<li><code>members</code>也是<code>KClass</code>中的一个属性，它包含了所有类的方法和属性。</li>
<li><code>filterIsInstance()</code>是<code>Iterable</code>接口的扩展函数，用于过滤出集合中指定的类型。</li>
<li><code>to</code>是一个<code>infix</code>扩展函数，它的定义如下：</li>
</ul>
<pre><code>public infix fun &lt;A, B&gt; A.to(that: B): Pair&lt;A, B&gt; = Pair(this, that)
</code></pre>
<ul>
<li>带有<code>infix</code>标识的函数只允许带有一个参数，并且在调用时可以省略包裹参数的括号。这种语法叫<code>中缀表达式</code></li>
</ul>
<p>写段测试代码，结合上一节的打印 map 函数看下效果：</p>
<pre><code>data class Person(var name: String, var age: Int)

Person(&quot;Peter&quot;, 16).ofMap()?.print &#123; it.toString() &#125;.let &#123; Log.v(&quot;test&quot;,&quot;$it&quot;) &#125;
</code></pre>
<p>测试代码先将<code>Person</code>实例转换成 map，然后打印 map。输出结果如下：</p>
<pre><code>V/test:
    &#123;
        [age] = 16
        [name] = Peter
    &#125;
</code></pre>
<p>若<code>data class</code>嵌套会发生什么？</p>
<pre><code>//&#39;位置，嵌套在Person类中&#39;
data class Location(var x: Int, var y: Int)
data class Person(var name: String, var age: Int, var locaton: Location? = null)

Person(&quot;Peter&quot;, 16, Location(20, 30)).ofMap()?.print &#123; it.toString() &#125;.let &#123; Log.v(&quot;test&quot;, &quot;$it&quot;) &#125;

//&#39;打印结果如下&#39;
    &#123;
        [age] = 16
        [locaton] = Location(x=20, y=30)
        [name] = Peter
    &#125;
</code></pre>
<p>期望得到类似 Json 的打印效果，但输出结果还差一点。是因为将<code>Person</code>转化成<code>Map</code>时并没有将嵌套的<code>Location</code>也转化成键值对。</p>
<p>需要将<code>ofMap()</code>方法重构成递归调用：</p>
<pre><code>fun Any.ofMap(): Map&lt;String, Any?&gt;? &#123;
    return this::class.takeIf &#123; it.isData &#125;
        ?.members?.filterIsInstance&lt;KProperty&lt;Any&gt;&gt;()
        ?.map &#123; member -&gt;
            val value = member.call(this)?.let &#123; v-&gt;
                //&#39;若成员变量是data class，则递归调用ofMap()，将其转化成键值对，否则直接返回值&#39;
                if (v::class.isData) v.ofMap()
                else v
            &#125;
            member.name to value
        &#125;
        ?.toMap()
&#125;
</code></pre>
<p>为了让打印结果也有嵌套缩进效果，打印 Map 的函数也需要相应地重构：</p>
<pre><code>/**
 * 打印 Map，生成结构化键值对子串
 * @param space 行缩进量
 */
fun &lt;K, V&gt; Map&lt;K, V?&gt;.print(space: Int = 0): String &#123;
    //&#39;生成当前层次的行缩进，用space个空格表示，当前层次每一行内容都需要带上缩进&#39;
    val indent = StringBuilder().apply &#123;
        repeat(space) &#123; append(&quot; &quot;) &#125;
    &#125;.toString()
    return StringBuilder(&quot;\n$&#123;indent&#125;&#123;&quot;).also &#123; sb -&gt;
        this.iterator().forEach &#123; entry -&gt;
            //&#39;如果值是 Map 类型，则递归调用print()生成其结构化键值对子串，否则返回值本身&#39;
            val value = entry.value.let &#123; v -&gt;
                (v as? Map&lt;*, *&gt;)?.print(&quot;$&#123;indent&#125;$&#123;entry.key&#125; = &quot;.length) ?: v.toString()
            &#125;
            sb.append(&quot;\n\t$&#123;indent&#125;[$&#123;entry.key&#125;] = $value,&quot;)
        &#125;
        sb.append(&quot;\n$&#123;indent&#125;&#125;&quot;)
    &#125;.toString()
&#125;
</code></pre>
<p>写段测试代码，看看效果：</p>
<pre><code>//&#39;坐标类，嵌套在Location类中&#39;
data class Coordinate(var x: Int, var y: Int)
//&#39;位置类，嵌套在Person类中&#39;
data class Location(var country: String, var city: String, var coordinate: Coordinate)
data class Person(var name: String, var age: Int, var locaton: Location? = null)

Person(&quot;Peter&quot;, 16, Location(&quot;china&quot;, &quot;shanghai&quot;, Coordinate(10, 20))).ofMap()?.print().let &#123; Log.v(&quot;test&quot;, &quot;$it&quot;) &#125;

//&#39;打印如下&#39;
    &#123;
        [age] = 16,
        [locaton] = 
              &#123;
                  [city] = shanghai,
                  [coordinate] = 
                           &#123;
                               [x] = 10,
                               [y] = 20,
                           &#125;,
                  [country] = china,
              &#125;,
        [name] = Peter,
    &#125;
</code></pre>
<h2 id="获取当前周一和周日"><a href="#获取当前周一和周日" class="headerlink" title="获取当前周一和周日"></a>获取当前周一和周日</h2><p>Java 系统默认一周的第一天是周日，最后一天是周六。</p>
<p>若需要做类似“一周一次提醒”的功能，就可以本地化提醒时间，然后在每次触发提醒时把它和当前周一作比较，大于则说明本周已提醒。</p>
<pre><code>fun thisMondayInMillis() = Calendar.getInstance().let &#123; c -&gt;
    //&#39;如果当前是周天，则减一天，计算周六所在周的周一&#39;
    if (c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) c.add(Calendar.DATE, -1)
    c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
    c.set(Calendar.HOUR_OF_DAY, 0)
    c.set(Calendar.MINUTE, 0)
    c.set(Calendar.SECOND, 0)
    c.set(Calendar.MILLISECOND, 0)
    c.timeInMillis
&#125;

fun thisSundayInMillis() = Calendar.getInstance().let &#123; c -&gt;
    //&#39;如果不是周天，则将日期调整到当前周的周六，然后在加一天&#39;
    if (c.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) &#123;
        c.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY)
        c.add(Calendar.DATE, 1)
    &#125;
    c.set(Calendar.HOUR_OF_DAY, 0)
    c.set(Calendar.MINUTE, 0)
    c.set(Calendar.SECOND, 0)
    c.set(Calendar.MILLISECOND, 0)
    c.timeInMillis
&#125;
</code></pre>
<h2 id="RecyclerView表项点击监听器"><a href="#RecyclerView表项点击监听器" class="headerlink" title="RecyclerView表项点击监听器"></a>RecyclerView表项点击监听器</h2><p><code>RecyclerView</code>没有子控件点击事件监听器，那就用<code>Kotlin扩展方法</code>扩展一个：</p>
<pre><code>//&#39;为 RecyclerView 扩展表项点击监听器&#39;
fun RecyclerView.setOnItemClickListener(listener: (View, Int) -&gt; Unit) &#123;
    //&#39;为 RecyclerView 子控件设置触摸监听器&#39;
    addOnItemTouchListener(object : RecyclerView.OnItemTouchListener &#123;
        //&#39;构造手势探测器，用于解析单击事件&#39;
        val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener &#123;
            override fun onShowPress(e: MotionEvent?) &#123;
            &#125;

            override fun onSingleTapUp(e: MotionEvent?): Boolean &#123;
                //&#39;当单击事件发生时，寻找单击坐标下的子控件，并回调监听器&#39;
                e?.let &#123;
                    findChildViewUnder(it.x, it.y)?.let &#123; child -&gt;
                        listener(child, getChildAdapterPosition(child))
                    &#125;
                &#125;
                return false
            &#125;

            override fun onDown(e: MotionEvent?): Boolean &#123;
                return false
            &#125;

            override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean &#123;
                return false
            &#125;

            override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean &#123;
                return false
            &#125;

            override fun onLongPress(e: MotionEvent?) &#123;
            &#125;
        &#125;)

        override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) &#123;

        &#125;

        //&#39;在拦截触摸事件时，解析触摸事件&#39;
        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean &#123;
            gestureDetector.onTouchEvent(e)
            return false
        &#125;

        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) &#123;
        &#125;
    &#125;)
&#125;</code></pre>

        </div>

    </div>

    

    

    

    

    

    
<nav class="article-nav">
  
    <a href="/2020/06/14/%E7%82%AB%E9%85%B7%EF%BC%81%E4%BB%8E%E6%9C%AA%E8%A7%81%E8%BF%87%E5%A6%82%E6%AD%A4Q%E5%BC%B9%E7%9A%84Switcher/" id="article-nav-newer" class="article-nav-link-wrap">
      <div class="article-nav-caption">下一篇</div>
      <div class="article-nav-title">
        
          炫酷！从未见过如此Q弹的Switcher
        
      </div>
    </a>
  
  
    <a href="/2020/06/14/2020%E5%B9%B4GitHub-%E4%B8%8A%E9%82%A3%E4%BA%9B%E4%BC%98%E7%A7%80Android%E5%BC%80%E6%BA%90%E5%BA%93/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-caption">上一篇</div>
      <div class="article-nav-title">2020年GitHub 上那些优秀Android开源库</div>
    </a>
  
</nav>


    <section class="share">
        <div class="share-title">分享</div>
        <a class="share-item" target="_blank"
            href="https://twitter.com/share?text=Kotlin实战 | 语法糖 - ApocalypseBlog&url=http://example.com/2020/06/14/Kotlin%E5%AE%9E%E6%88%98-%E8%AF%AD%E6%B3%95%E7%B3%96/">
            <box-icon type='logo' name='twitter'></box-icon>
        </a>
        <a class="share-item" target="_blank"
            href="https://www.facebook.com/sharer.php?title=Kotlin实战 | 语法糖 - ApocalypseBlog&u=http://example.com/2020/06/14/Kotlin%E5%AE%9E%E6%88%98-%E8%AF%AD%E6%B3%95%E7%B3%96/">
            <box-icon name='facebook-square' type='logo' ></box-icon>
        </a>
        <!-- <a class="share-item" target="_blank"
            href="https://service.weibo.com/share/share.php?title=Kotlin实战 | 语法糖 - ApocalypseBlog&url=http://example.com/2020/06/14/Kotlin%E5%AE%9E%E6%88%98-%E8%AF%AD%E6%B3%95%E7%B3%96/&pic=">
            <div class="n-icon n-icon-weibo"></div>
        </a> -->
    </section>

</article>









</div>
                </section>
            </section>

             
            <aside class="sidebar">
            
                
            </aside>
        </div>
    </div>

    <footer class="footer">
    <div class="footer-wave">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="#3c4859" fill-opacity="1" d="M0,160L60,181.3C120,203,240,245,360,240C480,235,600,181,720,186.7C840,192,960,256,1080,261.3C1200,267,1320,213,1380,186.7L1440,160L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path></svg>
    </div>

    <div class="footer-wrap">
        <div class="footer-inner"> 
            ApocalypseBlog &copy; 2021<br>
            Powered By Hexo · Theme By <a href="https://github.com/lh1me/hexo-theme-aomori" target="_blank">Aomori</a>
        </div>
    </div>

</footer>




<script src="/dist/build.js?1.14.0.js"></script>


<script src="/dist/custom.js?1.14.0.js"></script>









</body>

</html>